package nacos

import (
	"context"
	"fmt"
	"sync"

	"gitee.com/zawei/yggdrasil/pkg/log"
	"gitee.com/zawei/yggdrasil/pkg/registry"
	"gitee.com/zawei/yggdrasil/pkg/transport/server"
	"github.com/nacos-group/nacos-sdk-go/clients"
	"github.com/nacos-group/nacos-sdk-go/clients/naming_client"
	"github.com/nacos-group/nacos-sdk-go/model"
	"github.com/nacos-group/nacos-sdk-go/vo"
)

const (
	metadataScheme  = "scheme"
	metadataKind    = "kind"
	metadataAppID   = "appID"
	metadataAppName = "appName"
)

type subscribe struct {
	ServiceName string
	GroupName   string
}

type nacosRegistry struct {
	client  naming_client.INamingClient
	watched sync.Map
}

func (r *nacosRegistry) Register(ctx context.Context, info *server.Info) error {
	errs := make([]string, 0)
	metadata := info.Metadata
	if metadata == nil {
		metadata = make(map[string]string)
	}
	metadata[metadataScheme] = info.Scheme
	metadata[metadataKind] = info.Kind.String()
	if info.AppName != "" {
		metadata[metadataAppName] = info.AppName
	}
	if info.AppID != "" {
		metadata[metadataAppID] = info.AppID
	}
	for _, service := range info.Services {
		md := make(map[string]string)
		for k, v := range metadata {
			md[k] = v
		}
		for k, v := range service.Metadata {
			md[k] = v
		}
		_, err := r.client.RegisterInstance(vo.RegisterInstanceParam{
			Ip:          info.Ip,
			Port:        info.Port,
			Weight:      info.Weight,
			Enable:      !info.Disable,
			Healthy:     info.Healthy,
			Metadata:    md,
			ClusterName: info.Cluster,
			ServiceName: service.Name,
			GroupName:   info.Group,
			Ephemeral:   info.Ephemeral,
		})
		if err != nil {
			log.Errorf("fault to register service, service_name[%s], error: %s", service.Name, err.Error())
			errs = append(errs, fmt.Sprintf("%s.%s", info.Group, service.Name))
		}
	}
	if len(errs) > 0 {
		return fmt.Errorf("fault to register service: [%v]", errs)
	}
	return nil
}

func (r *nacosRegistry) Deregister(ctx context.Context, info *server.Info) error {
	errs := make([]string, 0)
	for _, service := range info.Services {
		_, err := r.client.DeregisterInstance(vo.DeregisterInstanceParam{
			Ip:          info.Ip,
			Port:        info.Port,
			ServiceName: service.Name,
			GroupName:   info.Group,
			Ephemeral:   info.Ephemeral,
		})
		if err != nil {
			log.Errorf("fault to deregister service, service_name[%s], error: %s", service.Name, err.Error())
			errs = append(errs, fmt.Sprintf("%s.%s", info.Group, service.Name))
		}

	}
	if len(errs) > 0 {
		return fmt.Errorf("fault to deregister service: [%v]", errs)
	}
	return nil
}

func (r *nacosRegistry) ListServices(ctx context.Context, name string, group string) (*registry.Endpoints, error) {
	service, err := r.client.GetService(vo.GetServiceParam{
		ServiceName: name,
		GroupName:   group,
	})
	if err != nil {
		return nil, err
	}
	endpoints := &registry.Endpoints{
		Group: group,
		Service: server.Service{
			Name:     service.Name,
			Metadata: service.Metadata,
		},
		Instances: make([]server.Instance, len(service.Hosts)),
	}
	for i, instance := range service.Hosts {
		endpoints.Instances[i] = server.Instance{
			Ip:        instance.Ip,
			Port:      instance.Port,
			Weight:    instance.Weight,
			Disable:   !instance.Enable,
			Healthy:   instance.Healthy,
			Metadata:  instance.Metadata,
			Cluster:   instance.ClusterName,
			Ephemeral: instance.Ephemeral,
		}
		endpoints.Instances[i].AppID, _ = instance.Metadata[metadataAppID]
		endpoints.Instances[i].AppName, _ = instance.Metadata[metadataAppName]
	}
	return endpoints, nil
}

func (r *nacosRegistry) Watch(ctx context.Context, name string, group string) (<-chan *registry.Endpoints, error) {
	c := make(chan *registry.Endpoints, 10)
	err := r.client.Subscribe(&vo.SubscribeParam{
		ServiceName: name,
		GroupName:   group,
		SubscribeCallback: func(services []model.SubscribeService, err error) {
			if err != nil {
				log.Errorf("fault to watch service, name[%s], group[%s], error: %s", name, group, err.Error())
				return
			}
			e, err := r.ListServices(ctx, name, group)
			if err != nil {
				log.Errorf("fault to get service, name[%s], group[%s], error: %s", name, group, err.Error())
				return
			}
			c <- e
		},
	})
	if err != nil {
		return nil, err
	}
	r.watched.Store(fmt.Sprintf("%s@%s", group, name), &subscribe{
		ServiceName: name,
		GroupName:   group,
	})
	return c, nil
}

func (r *nacosRegistry) Close() error {
	r.watched.Range(func(key, value interface{}) bool {
		v := value.(*subscribe)
		err := r.client.Unsubscribe(&vo.SubscribeParam{
			ServiceName:       v.ServiceName,
			GroupName:         v.GroupName,
			SubscribeCallback: func(services []model.SubscribeService, err error) {},
		})
		if err != nil {
			log.Errorf("fault to unsubscribe, error: %s", err.Error())
		}
		return true
	})
	return nil
}

func (r *nacosRegistry) Name() string {
	return "nacos"
}

func newRegistry(cfg *Config) *nacosRegistry {
	namingClient, err := clients.NewNamingClient(
		vo.NacosClientParam{
			ClientConfig:  cfg.Client,
			ServerConfigs: cfg.Servers,
		},
	)
	if err != nil {
		log.Fatalf("fault to create registry, error: %s", err.Error())
	}
	r := &nacosRegistry{client: namingClient}

	return r
}
